using System;
using ch.ethz.ssh2.crypto.cipher;
using ch.ethz.ssh2.crypto.digest;
using ch.ethz.ssh2.log;
using ch.ethz.ssh2.packets;
using Org.BouncyCastle.Security;
namespace ch.ethz.ssh2.transport
{
	
	/// <summary> TransportConnection.
	/// 
	/// </summary>
	/// <author>  Christian Plattner
	/// </author>
	/// <version>  2.50, 03/15/10
	/// </version>
	public class TransportConnection
	{
		virtual public int PacketOverheadEstimate
		{
			get
			{
				// return an estimate for the paket overhead (for send operations)
				return 5 + 4 + (send_padd_blocksize - 1) + send_mac_buffer.Length;
			}
			
		}
		//UPGRADE_NOTE: Final was removed from the declaration of 'log '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
		//UPGRADE_NOTE: The initialization of  'log' was moved to static method 'ch.ethz.ssh2.transport.TransportConnection'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'"
		private static readonly Logger log;
		
		internal int send_seq_number = 0;
		
		internal int recv_seq_number = 0;
		
		internal CipherInputStream cis;
		
		internal CipherOutputStream cos;
		
		internal bool useRandomPadding = false;
		
		/* Depends on current MAC and CIPHER */
		
		internal MAC send_mac;
		
		internal byte[] send_mac_buffer;
		
		internal int send_padd_blocksize = 8;
		
		internal MAC recv_mac;
		
		internal byte[] recv_mac_buffer;
		
		internal byte[] recv_mac_buffer_cmp;
		
		internal int recv_padd_blocksize = 8;
		
		/* won't change */
		
		//UPGRADE_NOTE: Final was removed from the declaration of 'send_padding_buffer '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
		internal byte[] send_padding_buffer = new byte[256];
		
		//UPGRADE_NOTE: Final was removed from the declaration of 'send_packet_header_buffer '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
		internal byte[] send_packet_header_buffer = new byte[5];
		
		//UPGRADE_NOTE: Final was removed from the declaration of 'recv_padding_buffer '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
        internal readonly byte[] recv_padding_buffer = new byte[256];
		
		//UPGRADE_NOTE: Final was removed from the declaration of 'recv_packet_header_buffer '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
        internal readonly byte[] recv_packet_header_buffer = new byte[5];
		
		internal bool recv_packet_header_present = false;
		
		internal ClientServerHello csh;
		
		internal readonly SecureRandom rnd;
		
		public TransportConnection(System.IO.Stream input, System.IO.Stream output, SecureRandom rnd)
		{
			this.cis = new CipherInputStream(new NullCipher(), input);
			this.cos = new CipherOutputStream(new NullCipher(), output);
			this.rnd = rnd;
		}
		
		public virtual void  changeRecvCipher(BlockCipher bc, MAC mac)
		{
			cis.changeCipher(bc);
			recv_mac = mac;
			recv_mac_buffer = (mac != null)?new byte[mac.size()]:null;
			recv_mac_buffer_cmp = (mac != null)?new byte[mac.size()]:null;
			recv_padd_blocksize = bc.BlockSize;
			if (recv_padd_blocksize < 8)
				recv_padd_blocksize = 8;
		}
		
		public virtual void  changeSendCipher(BlockCipher bc, MAC mac)
		{
			if ((bc is NullCipher) == false)
			{
				/* Only use zero byte padding for the first few packets */
				useRandomPadding = true;
				/* Once we start encrypting, there is no way back */
			}
			
			cos.changeCipher(bc);
			send_mac = mac;
			send_mac_buffer = (mac != null)?new byte[mac.size()]:null;
			send_padd_blocksize = bc.BlockSize;
			if (send_padd_blocksize < 8)
				send_padd_blocksize = 8;
		}
		
		public virtual void  sendMessage(byte[] message)
		{
			sendMessage(message, 0, message.Length, 0);
		}
		
		public virtual void  sendMessage(byte[] message, int off, int len)
		{
			sendMessage(message, off, len, 0);
		}
		
		public virtual void  sendMessage(byte[] message, int off, int len, int padd)
		{
			if (padd < 4)
				padd = 4;
			else if (padd > 64)
				padd = 64;
			
			int packet_len = 5 + len + padd; /* Minimum allowed padding is 4 */
			
			int slack = packet_len % send_padd_blocksize;
			
			if (slack != 0)
			{
				packet_len += (send_padd_blocksize - slack);
			}
			
			if (packet_len < 16)
				packet_len = 16;
			
			int padd_len = packet_len - (5 + len);
			
			if (useRandomPadding)
			{
				for (int i = 0; i < padd_len; i = i + 4)
				{
					/*
					* don't waste calls to rnd.nextInt() (by using only 8bit of the
					* output). just believe me: even though we may write here up to 3
					* bytes which won't be used, there is no "buffer overflow" (i.e.,
					* arrayindexoutofbounds). the padding buffer is big enough =) (256
					* bytes, and that is bigger than any current cipher block size + 64).
					*/
					
					//UPGRADE_TODO: Method 'java.util.Random.nextInt' was converted to 'System.Random.Next' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073'"
					int r = rnd.Next();
					send_padding_buffer[i] = (byte) r;
					send_padding_buffer[i + 1] = (byte) (r >> 8);
					send_padding_buffer[i + 2] = (byte) (r >> 16);
					send_padding_buffer[i + 3] = (byte) (r >> 24);
				}
			}
			else
			{
				/* use zero padding for unencrypted traffic */
				for (int i = 0; i < padd_len; i++)
					send_padding_buffer[i] = 0;
				/* Actually this code is paranoid: we never filled any
				* bytes into the padding buffer so far, therefore it should
				* consist of zeros only.
				*/
			}
			
			send_packet_header_buffer[0] = (byte) ((packet_len - 4) >> 24);
			send_packet_header_buffer[1] = (byte) ((packet_len - 4) >> 16);
			send_packet_header_buffer[2] = (byte) ((packet_len - 4) >> 8);
			send_packet_header_buffer[3] = (byte) ((packet_len - 4));
			send_packet_header_buffer[4] = (byte) padd_len;
			
			cos.write(send_packet_header_buffer, 0, 5);
			cos.write(message, off, len);
			cos.write(send_padding_buffer, 0, padd_len);
			
			if (send_mac != null)
			{
				send_mac.initMac(send_seq_number);
				send_mac.update(send_packet_header_buffer, 0, 5);
				send_mac.update(message, off, len);
				send_mac.update(send_padding_buffer, 0, padd_len);
				
				send_mac.getMac(send_mac_buffer, 0);
				cos.writePlain(send_mac_buffer, 0, send_mac_buffer.Length);
			}
			
			cos.flush();
			
			if (log.Enabled)
			{
				log.log(90, "Sent " + Packets.getMessageName(message[off] & 0xff) + " " + len + " bytes payload");
			}
			
			send_seq_number++;
		}
		
		public virtual int peekNextMessageLength()
		{
			if (recv_packet_header_present == false)
			{
				cis.read(recv_packet_header_buffer, 0, 5);
				recv_packet_header_present = true;
			}
			
			int packet_length = ((recv_packet_header_buffer[0] & 0xff) << 24) | ((recv_packet_header_buffer[1] & 0xff) << 16) | ((recv_packet_header_buffer[2] & 0xff) << 8) | ((recv_packet_header_buffer[3] & 0xff));
			
			int padding_length = recv_packet_header_buffer[4] & 0xff;
			
			if (packet_length > 35000 || packet_length < 12)
				throw new System.IO.IOException("Illegal packet size! (" + packet_length + ")");
			
			int payload_length = packet_length - padding_length - 1;
			
			if (payload_length < 0)
				throw new System.IO.IOException("Illegal padding_length in packet from remote (" + padding_length + ")");
			
			return payload_length;
		}
		
		public virtual int receiveMessage(byte[] buffer, int off, int len)
		{
			if (recv_packet_header_present == false)
			{
				cis.read(recv_packet_header_buffer, 0, 5);
			}
			else
				recv_packet_header_present = false;
			
			int packet_length = ((recv_packet_header_buffer[0] & 0xff) << 24) | ((recv_packet_header_buffer[1] & 0xff) << 16) | ((recv_packet_header_buffer[2] & 0xff) << 8) | ((recv_packet_header_buffer[3] & 0xff));
			
			int padding_length = recv_packet_header_buffer[4] & 0xff;
			
			if (packet_length > 35000 || packet_length < 12)
				throw new System.IO.IOException("Illegal packet size! (" + packet_length + ")");
			
			int payload_length = packet_length - padding_length - 1;
			
			if (payload_length < 0)
				throw new System.IO.IOException("Illegal padding_length in packet from remote (" + padding_length + ")");
			
			if (payload_length >= len)
				throw new System.IO.IOException("Receive buffer too small (" + len + ", need " + payload_length + ")");
			
			cis.read(buffer, off, payload_length);
			cis.read(recv_padding_buffer, 0, padding_length);
			
			if (recv_mac != null)
			{
				cis.readPlain(recv_mac_buffer, 0, recv_mac_buffer.Length);
				
				recv_mac.initMac(recv_seq_number);
				recv_mac.update(recv_packet_header_buffer, 0, 5);
				recv_mac.update(buffer, off, payload_length);
				recv_mac.update(recv_padding_buffer, 0, padding_length);
				recv_mac.getMac(recv_mac_buffer_cmp, 0);
				
				for (int i = 0; i < recv_mac_buffer.Length; i++)
				{
					if (recv_mac_buffer[i] != recv_mac_buffer_cmp[i])
						throw new System.IO.IOException("Remote sent corrupt MAC.");
				}
			}
			
			recv_seq_number++;
			
			if (log.Enabled)
			{
				log.log(90, "Received " + Packets.getMessageName(buffer[off] & 0xff) + " " + payload_length + " bytes payload");
			}
			
			return payload_length;
		}
		static TransportConnection()
		{
			log = Logger.getLogger(typeof(TransportConnection));
		}
	}
}